﻿using System;

using System.Collections.Generic;
using Silvermoon.Controls;
using Silvermoon.OpenGL;
using System.Drawing;
using Silvermoon.Images;
using Silvermoon.Visuals;
using System.IO;
using Silvermoon.Controls.Classes;
using System.Threading;
using Silvermoon;
using Silvermoon.Controls.Base;
using System.Reflection;
using Silvermoon.Animations;
using Silvermoon.OpenGL.Native;
using Silvermoon.Controls.Interfaces;

namespace FileExplorer
{
    /// <summary>
    /// The File item displays either a file or a folder and prefetches child folders.
    /// </summary>
    public class FileItem : TreeViewItem, IToolTip, IComparer<string>
    {
        #region const

        public static readonly int DelBtnSize = 64;
        public static readonly int ItemHeight = 88;

        #endregion
        #region fields

        private static Texture normalDelImage;
        private static Texture pressedDelImage;
        private static GridDefinition gridDefinition;
        private static GridDefinition normalState;
        private static GridDefinition editState;
        private static GridDefinition editSelectedState;
        private static GridDefinition confirmDeleteState;


        private bool delState;
        private Grid grid;
        private Label dateModifiedLabel;
        private Label sizeLabel;
        private Texture texture;
        private PictureBox image;
        private Label label;
        private Icon icon;
        private TextBox textBox;
        private ImageButton delBtn;
        private Button btn;
        private ScrollPanel p;
        private Button confirmDelBtn;

        #endregion
        #region props

        public bool DelState
        {
            get { return delState; }
            set
            {
                if (delState != value)
                {
                    delState = value;
                    Invalidate(InvalidationFlag.LayoutAppearance);
                }
            }
        }

        public static Texture GetNormalDelImage()
        {
            if (normalDelImage == null)
            {
                normalDelImage = new Texture();
                Stream s = Assembly.GetCallingAssembly().GetManifestResourceStream("Demo.Images.Remove.png");
                normalDelImage.LoadFromStream(s);
            }
            return normalDelImage;
        }

        public static Texture GetPressedDelImage()
        {
            if (pressedDelImage == null)
            {
                pressedDelImage = new Texture();
                Stream s = Assembly.GetCallingAssembly().GetManifestResourceStream("Demo.Images.RemovePressed.png");
                pressedDelImage.LoadFromStream(s);
            }
            return pressedDelImage;
        }

        public FileItemMode Modes { get; private set; }

        public FileItemMode Mode { get { return FileItemMode.Modes; } }


        public List<string> Files { get; set; }

        public int FileOffset = 0;

        /// <summary>
        /// Gets or sets whether the folder of this FileItem has been populated.
        /// </summary>
        public bool Populated { get; set; }

        /// <summary>
        /// Gets or sets the path of the file/folder.
        /// </summary>
        public string Path { get; private set; }

        /// <summary>
        /// Gets whether the FileItem displays a file.
        /// </summary>
        public bool IsFile { get; private set; }

        /// <summary>
        /// Gets whether the FileItem displays a folder.
        /// </summary>
        public bool IsFolder { get { return !IsFile; } }

        public string FileSize { get { return sizeLabel.Text; } set { sizeLabel.Text = value; Invalidate(InvalidationFlag.LayoutAppearance); } }

        public string DateModified { get { return dateModifiedLabel.Text; } set { dateModifiedLabel.Text = value; Invalidate(InvalidationFlag.LayoutAppearance); } }

        /// <summary>
        /// Gets or sets the Icon of the file folder.
        /// </summary>
        public Icon Icon
        {
            get { return icon; }
            set
            {
                if (icon != value)
                {
                    icon = value;
                    Invalidate(InvalidationFlag.LayoutAppearance);
                }
            }
        }

        public ToolTipBase ToolTip { get; private set; }



        #endregion
        #region ctor/dtor

        static FileItem()
        {
            CreateStates();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="FileItem"/> class.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <param name="isDir">if set to <c>true</c> [is dir].</param>
        public FileItem(string path, bool isDir)
            : base()
        {
            grid = new Grid();
            grid.Margin = new Margin(0);
            grid.Definition = gridDefinition;
            grid.State = normalState;

            Controls.Add(grid);

            Modes = FileItemMode.Modes;
            this.Path = path;
            image = new PictureBox { Size = new Size(48, 48), Margin = new Margin(0, (ItemHeight - 48) / 2, 0, null) };
            label = new Label { LineMode = LineMode.WordWrap, LineAlignment = Alignment.Center, Margin = new Margin(4, 2, 4, 2) };
            dateModifiedLabel = new Label
            {
                ScaleFactor = 0.85f,
                Alpha = 150,
                Alignment = Alignment.Far,
                LineAlignment = Alignment.Near,
                Margin = new Margin(0, 2, 4, 2)
            };

            sizeLabel = new Label
            {
                ScaleFactor = 0.85f,
                Alpha = 150,
                Alignment = Alignment.Far,
                LineAlignment = Alignment.Far,
                Margin = new Margin(0, 2, 4, 2)
            };

            textBox = new TextBox
            {
                Height = 56,
                Margin = new Margin(4, (ItemHeight - 56) / 2, 16, null),
            };
            textBox.TextChanged += (s, e) => label.Text = textBox.Text;

            delBtn = new ImageButton
            {
                Color = Color.White,
                Image = GetNormalDelImage(),
                PressedImage = GetPressedDelImage(),
                Size = new Size(48, 48),
                Margin = new Margin(0, (ItemHeight - 48) / 2, 0, null)
            };

            delBtn.Click += (sender, e) => this.DelState ^= true;

            confirmDelBtn = new Button { Text = "Delete?", Color = Color.Red, Style = RenderStyle.Chrome, TextColor = Color.White, Margin = new Margin(-4, 16, null, 16), Size = new Size(96, 48) };
            confirmDelBtn.Click += (sender, e) => this.ListView.Items.Remove(this);


            grid.Controls.Add(0, delBtn);
            grid.Controls.Add(1, image);
            grid.Controls.Add(2, label);
            grid.Controls.Add(3, textBox);
            grid.Controls.Add(4, dateModifiedLabel);
            grid.Controls.Add(5, sizeLabel);
            grid.Controls.Add(6, confirmDelBtn);

            textBox.Text = label.Text = System.IO.Path.GetFileName(path);
            label.Font = isDir ? FontSprite.Bold : FontSprite.Regular;
            this.Size = new Size(Width, ItemHeight);

            IsFile = !isDir;
            Title = label.Text;
            image.Image = SysImages.CheckBox;
            image.Visible = true;
            HasItems = isDir;

            // to avoid a delay, we populate the details of the file/folder in a separate thread with lower priority:
            ThreadPool.QueueUserWorkItem((o) => { Thread.CurrentThread.Priority = ThreadPriority.Lowest; LoadFileData(); });

            if (isDir)
            {
                Button b = new Button { Text = "Edit", Toggle = true, ShadowedText = true, Style = RenderStyle.Chrome, Color = Color.FromArgb(48, 48, 48) };
                b.DownChanged += (s, e) => Modes.EditMode = (s as ButtonBase).IsDown;
                b.Size = new Size(80, 54);
                Button = b;

            }

            ToolTip toolTip = new ToolTip { ButtonSize = new Size(64, 64), Enabled = false };
            toolTip.Buttons.Add(new ImageButton { Image = SysImages.Keyboard, PressedColor = Color.Red });
            toolTip.Buttons.Add(new ImageButton { Image = SysImages.MenuBackground, PressedColor = Color.Red });
            ToolTip = toolTip;
        }


        /// <summary>
        /// On Dispose, we dispose everything we have created, except the controls and visuals that are accessible by Controls and Visuals, which will be disposed automatically:
        /// </summary>
        public override void Dispose()
        {
            if (texture != null) texture.Dispose();
            base.Dispose();
        }

        #endregion
        #region methods


        /// <summary>
        /// Creates all states that the file item can have.
        /// States allow animated transition from one state to another by just specifying the layout for each state of a grid.
        /// </summary>
        private static void CreateStates()
        {
            const int delBtnIndex = 0;
            const int iconIndex = 1;

            GridDefinition g;
            g = new GridDefinition();
            g.Rows.AddRange(ItemHeight / 2, ItemHeight / 2);
            g.Columns.AddRange(48, 48, 1f, 100, 100);
            g.CreateCell(0, 0, 1, 2); // 0 = del btn
            g.CreateCell(1, 0, 1, 2); // 1 = icon
            g.CreateCell(2, 0, 1, 2); // 2 = label;
            g.CreateCell(2, 0, 3, 2).Alpha = 0; // 3 = textBox;
            g.CreateCell(3, 0, 2, 1); // 4=date;
            g.CreateCell(3, 1, 2, 1); // 5 = size;
            g.CreateCell(4, 0, 1, 2).Alpha = 0; // 6 = confirm button
            g.Cells[delBtnIndex].ResizeMode = CellResizeMode.Stretched;
            g.Cells[iconIndex].ResizeMode = CellResizeMode.Stretched;
            gridDefinition = g;

            g = new GridDefinition();
            g.Rows.AddRange(ItemHeight / 2, ItemHeight / 2);
            g.Columns.AddRange(0, 48, 1f, 100, 0);
            g.CreateCell(0, 0, 1, 2); // 0 = del btn
            g.CreateCell(1, 0, 1, 2); // 1 = icon
            g.CreateCell(2, 0, 1, 2); // 2 = label;
            g.CreateCell(2, 0, 3, 2).Alpha = 0; // 3 = textBox;
            g.CreateCell(3, 0, 2, 1); // 4=date;
            g.CreateCell(3, 1, 2, 1); // 5 = size;
            g.CreateCell(4, 0, 1, 2).Alpha = 0; // 6 = confirm button
            g.Cells[delBtnIndex].ResizeMode = CellResizeMode.Stretched;
            g.Cells[iconIndex].ResizeMode = CellResizeMode.Stretched;
            normalState = g;

            g = new GridDefinition();
            g.Rows.AddRange(ItemHeight / 2, ItemHeight / 2);
            g.Columns.AddRange(48, 0, 1f, 100, 0);
            g.CreateCell(0, 0, 1, 2); // 0 = del btn
            g.CreateCell(1, 0, 1, 2); // 1 = icon
            g.CreateCell(2, 0, 1, 2).Alpha = 255; // 2 = label;
            g.CreateCell(2, 0, 3, 2).Alpha = 0; // 3 = textBox;
            g.CreateCell(3, 0, 2, 1); // 4=date;
            g.CreateCell(3, 1, 2, 1); // 5 = size;
            g.CreateCell(4, 0, 1, 2).Alpha = 0; // 6 = confirm button
            g.Cells[delBtnIndex].ResizeMode = CellResizeMode.Stretched;
            g.Cells[iconIndex].ResizeMode = CellResizeMode.Stretched;
            g.Cells[iconIndex].Alpha = 0;
            editState = g;

            g = new GridDefinition();
            g.Rows.AddRange(ItemHeight / 2, ItemHeight / 2);
            g.Columns.AddRange(48, 0, 1f, 100, 0);
            g.CreateCell(0, 0, 1, 2); // 0 = del btn
            g.CreateCell(1, 0, 1, 2); // 1 = icon
            g.CreateCell(2, 0, 1, 2).Alpha = 0; // 2 = label;
            g.CreateCell(2, 0, 3, 2).Alpha = 255; // 3 = textBox;
            g.CreateCell(3, 0, 2, 1).Alpha = 0; // 4=date;
            g.CreateCell(3, 1, 2, 1).Alpha = 0; // 5 = size;
            g.CreateCell(4, 0, 1, 2).Alpha = 0; // 6 = confirm button
            g.Cells[delBtnIndex].ResizeMode = CellResizeMode.Stretched;
            g.Cells[iconIndex].ResizeMode = CellResizeMode.Stretched;
            g.Cells[iconIndex].Alpha = 0;
            editSelectedState = g;

            g = new GridDefinition();
            g.Rows.AddRange(ItemHeight / 2, ItemHeight / 2);
            g.Columns.AddRange(48, 0, 1f, 0, 100);
            g.CreateCell(0, 0, 1, 2); // 0 = del btn
            g.CreateCell(1, 0, 1, 2); // 1 = icon
            g.CreateCell(2, 0, 1, 2).Alpha = 0; // 2 = label;
            g.CreateCell(2, 0, 2, 2).Alpha = 255; // 3 = textBox;
            g.CreateCell(3, 0, 2, 1).Alpha = 0; // 4=date;
            g.CreateCell(3, 1, 2, 1).Alpha = 0; // 5 = size;
            g.CreateCell(4, 0, 1, 2).Alpha = 255; // 6 = confirm button
            g.Cells[delBtnIndex].ResizeMode = CellResizeMode.Stretched;
            g.Cells[iconIndex].ResizeMode = CellResizeMode.Stretched;
            g.Cells[iconIndex].Alpha = 0;
            confirmDeleteState = g;
        }


        private void LoadFileData()
        {
            if (IsFile)
            {
                FileInfo info = new FileInfo(Path);
                FileSize = FileIconUtil.ToFileSize(info.Length);
                DateModified = info.LastWriteTime.ToShortDateString();
            }
            if (Icon == null)
            {
                Icon = FileIconUtil.GetIconFromPath(Path);
            }
        }

        public override void OnClick()
        {
            if (Mode.EditMode) return;
            if (IsFile)
            {
                // when we click on a file item, we show the details of the file in a new page.
                CreateDetailView();
            }
            if (IsFolder)
            {
                if (!(Items.Count != 0 && (!(Items[0] is WaitItem))))
                {
                    // when we click on a folder item, we add a WaitItem as only child item, so while transitioning to the list, we see the "Loading..." message:
                    AddWaitItem();
                }
            }
            base.OnClick();
        }

        /// <summary>
        /// Creates the detail view for a file and set it as Control for this item.
        /// </summary>
        private void CreateDetailView()
        {
            if (p == null)
            {
                p = new ScrollPanel();

                // sets the DetailViewPanel as control to be displayed when the treeview navigates to this item. By default this is the internal NodeListView of the Fileitem,
                // but we can change it here to navigate to something else:
                Control = p;

                {
                    // here we create the controls for the detail view:
                    int l = 16;
                    int w = this.Width - 2 * l;

                    Button b = new ChromeButton { Text = "OK" };
                    b.Size = new Size(54, 48);
                    this.Button = b;

                    TextBox tb = new TextBox { Text = System.IO.Path.GetFileName(Path) };
                    tb.Bounds = new Rectangle(l, 24, w, 54);
                    p.Controls.Add(tb);

                    b = new ChromeButton { Text = "Open" };
                    b.Bounds = new Rectangle(l, 88, w, 64);
                    b.Color = Color.Green;
                    btn = b;
                    b.Click += (s, e) => Execute();
                    p.Controls.Add(b);

                    Button openBtn = b;

                    b = new ChromeButton { Text = "Delete", };
                    b.Color = Color.DarkRed;
                    b.Bounds = new Rectangle(l, 164, w, 64);
                    btn = b;
                    b.Click += (s, e) => ConfirmDelete();
                    p.Controls.Add(b);

                    ProgressBar bar = new ProgressBar { Bounds = new Rectangle(l, 240, w, 48), Value = 45, ReadOnly = false, DisplayValue = true, Color=Color.Orange };
                    bar.GetDisplayText += (s,e) => e.Text+=" %";
                    p.Controls.Add(bar);


                    b = new ChromeButton { Text = "50 %", Color = Color.Black, Bounds = new Rectangle(l, 300, w, 64) };
                    b.Click += (s, e) => bar.ChangeTo(50);

                    p.Controls.Add(b);

                }
            }
        }

        /// <summary>
        /// Shows a modal dialog to confirm whether to delete this item.
        /// </summary>
        private void ConfirmDelete()
        {
            ConfirmDialog dlg = new ConfirmDialog("Delete file", "Do you really want to delete " + Path + "?");

            // now show the dialog modal. In Modal view, all other controls are disabled as long as the dialog is displayed.
            // when a DialogButton is pressed, the specified anonymous DialogResultEventHandler is executed:

            dlg.ShowModal(TransitionMode.ZoomIn | TransitionMode.FadeIn, (s, e) =>
                {
                    // here we check the result from the dialog and perform the appropriate action:

                    if (e.Result == DialogResult.OK)
                    {
                        // go back from details view to this item:
                        TreeView.GoBack();

                        // now remove the item:
                        this.ListView.Items.Remove(this);
                    }
                });
        }


        private void OnBtnDownChanged(object sender, EventArgs e)
        {
            Button b = (Button)sender;
            b.Color = b.IsPressed ? Color.Blue : SysColors.ButtonColor;
        }

        private void AddWaitItem()
        {
            // don't use virtual items while showing the physical node:
            BeginInit();
            WaitItem waitItem = new WaitItem { Text = "Loading..." };
            Items.Add(waitItem);
            EndInit();
        }

        private GridDefinition GetState()
        {
            if (Mode.EditMode)
            {
                if (IsSelected)
                {
                    if (delState) return confirmDeleteState; else return editSelectedState;
                }
                else
                {
                    delState = false;
                    return editState;
                }
            }
            else
            {
                delState = false;
                return normalState;
            }
        }

        /// <summary>
        /// This is the method where we actually layout the control, by adding/removing or modifying Visuals or Controls depending on the current properties.
        /// To avoid unnecassary changes, we can access the InvalidationMask that contains information about what has changed. So we can decide explicitely what we
        /// need to update. Note that with CheckFlags() we can easily query flags in the InvalidationMask that are set.
        /// </summary>
        protected override void OnLayout()
        {
            if (CheckFlag(InvalidationFlag.Appearance))
            {
                LoadIcon();
            }
            GridDefinition state = GetState();

            if ((state == editSelectedState && grid.State == editState) || (state == editState && grid.State == editSelectedState))
            {
                // do not animate if the state is changed between editSelectedState and editState.
                grid.State = state;
            }
            else
            {
                grid.ChangeState(state, 375);
            }

            base.OnLayout();
        }

        /// <summary>
        /// Loads the icon of the file/folder and creates a TextureElement that holds the image.
        /// </summary>
        private void LoadIcon()
        {
            if (icon != null && texture == null)
            {
                texture = new Texture();
                using (Graphics g = texture.CreateGraphics(icon.Width, icon.Height, GlesPixelFormat.RGBA, Color.Fuchsia))
                {
                    g.Clear(Color.Fuchsia);
                    g.DrawIcon(icon, 0, 0);
                }
                texture.ReleaseGraphics();
                image.Image = texture;
                image.Visible = true;

            }
        }


        /// <summary>
        /// just an example of how to add additional visuals to derived control:
        /// </summary>
        public override IEnumerable<Silvermoon.Visuals.Visual> Visuals
        {
            get
            {
                // yield return the base visuals:
                foreach (var c in GetParentVisuals()) yield return c;

                //now yield your own:
                // yield return myControl
            }
        }

        private IEnumerable<Visual> GetParentVisuals()
        {
            return base.Visuals;
        }

        public override void OnMouseUp(MouseUpEventArgs e)
        {
            base.OnMouseUp(e);

            // we accept a double click only when it is a file:
            e.AllowDoubleClick = IsFile;
        }

        public override void OnMouseClick(MouseClickEventArgs e)
        {
            if (!e.Handled && e.MouseClickType == MouseClickType.DoubleClick) OnDblClick();

            base.OnMouseClick(e);
        }

        private void OnDblClick()
        {
            Execute();
        }

        /// <summary>
        /// Executes the file.
        /// </summary>
        private void Execute()
        {
            try
            {
                if (IsFile) System.Diagnostics.Process.Start(Path, "");
            }
            catch (Exception e)
            {
                System.Windows.Forms.MessageBox.Show(e.Message);
            }
        }

        /// <summary>
        /// When the FileItem gets detached from the treeview, we perform some additional stuff:
        /// </summary>
        /// <param name="index"></param>
        public override void OnDetach(int index)
        {
            FileItem parent = this.ParentItem as FileItem;
            if (parent != null)
            {
                if (index >= parent.FileOffset) parent.FileOffset--;
                parent.Files.RemoveAt(index);
            }
            base.OnDetach(index);
        }

        /// <summary>
        /// This method is used to reset the data of a treeview item by deleting all child items.
        /// We can add additional things to reset here:
        /// </summary>
        public override void Reset()
        {
            base.Reset();
            Files = null;
            Populated = false;

        }


        /// <summary>
        /// Populates the folders in this folder.
        /// </summary>
        public void Populate()
        {
            BeginInit();
            Items.Clear();
            PopulateFolder();

            EndInit();
        }

        /// <summary>
        /// Populates a folder and prefetches the data.
        /// The FileItem for each file/folder is not created at this point, since it is faster not to create all FileItems at once.
        /// Instead we use the virtual mode which calls the OnAttachItem event each time the treeview needs to access a node.
        /// The only thing we do here is to determine how many files/folders are available, and since we do have to get their names,
        /// we cache them for later access when we indeed create a FileItem for it.
        /// </summary>
        private void PopulateFolder()
        {
            string path = this.Path;
            string[] dirs = Directory.GetDirectories(path);
            string[] dfiles = Directory.GetFiles(path);
            Array.Sort(dfiles);

            this.FileOffset = dirs.Length;
            Files = new List<string>(dirs.Length + dfiles.Length + 2 + 24);
            if (dirs.Length > 0)
            {
                FileOffset++;
                Files.Add("?Folders");
                Items.Add(new ItemHeader { Text = "Folders", Color = Color.Black });
            }

            foreach (var s in dirs) Files.Add(s);
            char idx = '0';
            int idxdiv = 100;

            foreach (var s in dfiles)
            {
                char c = Char.ToUpper(System.IO.Path.GetFileName(s)[0]);
                if (idx != c && idxdiv >= 5)
                {
                    // create an ItemHeader to separate a new Index.
                    // note that ItemHeaders should be attached to a virtual listview/treeview immediately, otherwise the treeview/listview has trouble
                    // do fetch the top header while browsing.
                    string headerText = c.ToString();
                    Items[Files.Count] = new ItemHeader { Text = headerText, Color = Color.Silver, TextColor = Color.DimGray };
                    Files.Add("?" + headerText);
                    idx = c;
                    idxdiv = 0;
                }
                else idxdiv++;
                Files.Add(s);
            }
            if (dirs.Length > 0) Files.Sort(1, dirs.Length, this);

        }

        #endregion
        #region IComparer<string> Members

        /// <summary>
        /// Just a helper method to compare to strings.
        int IComparer<string>.Compare(string x, string y)
        {
            return x.ToLower().CompareTo(y.ToLower());
        }

        #endregion

    }
}
